package com.yupi.project.filter;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.mdd.model.entity.InterfaceInfo;
import com.mdd.model.entity.User;
import com.mdd.service.InnerInterfaceInfoService;
import com.mdd.service.InnerUserInterfaceInfoService;
import com.mdd.service.InnerUserService;
import com.yupi.project.utils.SignUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.reactivestreams.Publisher;
import org.redisson.api.LockOptions;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class CustomGlobalFilter implements GlobalFilter ,Ordered{

    /**
     * 黑名单
     */
    private static final List<String> ID_BLACK = Arrays.asList("192.168.0.1","192.168.0.0");

    /**
     * 接口信息列表的key
     */
    private static final String INTERFACEINFO_LIST_KEY = "interfaceInfo:list";

    private static final String JUDGE_INVOKE_NUM_KEY_PREFIX = "lock:judge:num:";


    @DubboReference
    private InnerUserService innerUserService;
    
    @DubboReference
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;
    
    @DubboReference
    private InnerInterfaceInfoService innerInterfaceInfoService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 1.请求日志
        log.info("{");
        log.info("请求唯一标识:" + request.getId());
        log.info("请求URI:" + request.getURI());
        log.info("请求路径:" + request.getPath());
        log.info("请求方法:" + request.getMethod());
        log.info("请求参数:" + request.getQueryParams());
        log.info("请求来源地址:" + request.getRemoteAddress());
        log.info("}");
        // 2.访问控制 黑白名单
        String ip = request.getRemoteAddress().getHostName();
        if (ID_BLACK.contains(ip)) {
            //拦截
            return interceptorRequest(response);
        }
        // 3.权限控制 公钥密钥校验
        HttpHeaders headers = request.getHeaders();
        String accessKey = headers.get("accessKey").get(0);
        String nonce = headers.get("nonce").get(0);
        String timestamp = headers.get("timestamp").get(0);
        String body = headers.get("body").get(0);
        String clientSign = headers.get("sign").get(0);
        //校验公钥
        User invokeUser;
        try {
            invokeUser = innerUserService.getUser(accessKey);
        } catch (Exception e) {
            //公钥有误
            return interceptorRequest(response);
        }
        if (Long.parseLong(nonce) > 100000) {
            return interceptorRequest(response);
        }
        // 3.2不能超过五分钟
        long FIVE_MINUTES = 300000L;
        if (System.currentTimeMillis() - Long.parseLong(timestamp) > FIVE_MINUTES) {
            return interceptorRequest(response);
        }
        // secretKey 实际是通过查询数据库根据accessKey得到的
        // 签名认证
        String serverSign = SignUtils.getSign(body, invokeUser.getSecretKey());
        // 3.4签名不匹配
        if (!serverSign.equals(clientSign)) {
            return interceptorRequest(response);
        }
        // 4.校验接口是否存在
        //获取接口信息列表byRedis
        List<InterfaceInfo> interfaceInfoList = getInterfaceInfoListByRedis();
        String path = request.getPath().value();
        String url = getUrlByPath(interfaceInfoList, path);
        String method = request.getMethod().toString();
        InterfaceInfo interfaceInfo;
        try {
            interfaceInfo = innerInterfaceInfoService.getInterface(url, method);
        } catch (Exception e) {
            return interceptorRequest(response);
        }
        Long userId = invokeUser.getId();
        Long interfaceInfoId = interfaceInfo.getId();
        // 校验该用户是否还有该接口的调用次数
        // 加锁
        String key = JUDGE_INVOKE_NUM_KEY_PREFIX+invokeUser.getId().toString();
        RLock lock = redissonClient.getLock(key);
        boolean tryLock = false;
        try {
            tryLock = lock.tryLock(1, 3, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (!tryLock){
            //获取锁失败
            log.error("获取锁失败!");
            return interceptorRequest(response);
        }
        if (!innerUserInterfaceInfoService.judgeInvokeCount(userId, interfaceInfoId)) {
            log.error("该用户没有调用次数了");
            return interceptorRequest(response);
        }
        // 5.放行调用接口
        return handleResponse(exchange, chain,userId,interfaceInfoId,lock);
    }

    /**
     * 处理响应
     *
     * @param exchange
     * @param chain
     * @return
     */
    public Mono<Void> handleResponse(ServerWebExchange exchange, GatewayFilterChain chain,long userId,long interfaceInfoId,RLock lock) {
        try {
            ServerHttpResponse originalResponse = exchange.getResponse();
            // 缓存数据的工厂
            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
            // 拿到响应码
            HttpStatus statusCode = originalResponse.getStatusCode();
            if (statusCode == HttpStatus.OK) {
                // 装饰，增强能力
                ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                    // 等调用完转发的接口后才会执行
                    @Override
                    public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                        //log.info("body instanceof Flux: {}", (body instanceof Flux));
                        if (body instanceof Flux) {
                            Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                            // 往返回值里写数据
                            // 拼接字符串
                            return super.writeWith(
                                    fluxBody.map(dataBuffer -> {
                                        // 调用成功，接口调用次数 + 1 invokeCoun
                                        // 调用统计
                                        try {
                                            innerUserInterfaceInfoService.invokeCount(userId,interfaceInfoId);
                                        } catch (Exception e) {
                                            log.info("接口统计失败");
                                        }finally {
                                            //释放锁
                                            lock.unlockAsync();
                                        }
                                        byte[] content = new byte[dataBuffer.readableByteCount()];
                                        dataBuffer.read(content);
                                        DataBufferUtils.release(dataBuffer);//释放掉内存
                                        // 构建日志
                                        StringBuilder sb2 = new StringBuilder(200);
                                        List<Object> rspArgs = new ArrayList<>();
                                        rspArgs.add(originalResponse.getStatusCode());
                                        String data = new String(content, StandardCharsets.UTF_8); //data
                                        sb2.append(data);
                                        // 6.响应日志
                                        log.info("响应结果：" + data);
                                        return bufferFactory.wrap(content);
                                    }));
                        } else {
                            // 8. 调用失败，返回一个规范的错误码
                            log.error("<--- {} 响应code异常", getStatusCode());
                        }
                        return super.writeWith(body);
                    }
                };
                // 设置 response 对象为装饰过的
                return chain.filter(exchange.mutate().response(decoratedResponse).build());
            }
            return chain.filter(exchange); // 降级处理返回数据
        } catch (Exception e) {
            log.error("网关处理响应异常" + e);
            return chain.filter(exchange);
        }
    }


    /**
     * 拦截请求响应结果
     * @param response
     * @return
     */
    public Mono<Void> interceptorRequest(ServerHttpResponse response){
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.setComplete();
    }

    /**
     * 获取接口信息列表
     * @return
     */
    private List<InterfaceInfo> getInterfaceInfoListByRedis(){
        String s = stringRedisTemplate.opsForValue().get(INTERFACEINFO_LIST_KEY);
        return new Gson().fromJson(s, new TypeToken<List<InterfaceInfo>>(){ }.getType());
    }

    /**
     * 根据路径找到对应接口的url
     * @param list
     * @param path
     * @return
     */
    private String getUrlByPath(List<InterfaceInfo> list,String path){
        for (InterfaceInfo interfaceInfo : list) {
            if (path.equals(interfaceInfo.getPath())) {
                return interfaceInfo.getUrl();
            }
        }
        return null;
    }

    @Override
    public int getOrder() {
        return -1;
    }
}